package org.dfl.core.data;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.io.FilenameUtils;
import org.dfl.core.data.types.CollectionType;
import org.dfl.core.data.types.MapType;
import org.dfl.core.data.types.Type;
import org.dfl.core.data.types.TypeManager;
import org.dfl.core.model.Place;
import org.dfl.core.utils.ImageUtils;
import org.dfl.core.utils.Utils;
import org.dfl.messages.Messages;
import org.dfl.plugin.dialogs.DialogsManager;
import org.dfl.plugin.editor.model.DflDiagram;
import org.dfl.plugin.editor.model.PlaceModelElement;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;

public class IODataManager {
	/**
	 * sprawdza poprawnosc drzewa z danymi wejsciowymi z typem t
	 * jesli jakies dane wejsciowe sa w typie binarnym, to sa zaciagne z pliku o nazwie zdefiniowanej w elemencie <file>
	 * z katalogu inputDir
	 * @param inputDir	- katalog z plikami wejsciowymi
	 * @param root		- element glowny wejscia
	 * @param t			- typ danych wymagany na wejsciu
	 * @return
	 * @throws Exception
	 */
	@SuppressWarnings("unchecked") //$NON-NLS-1$
	private static boolean isValidDataElem(Element root, Type t) throws Exception{
		if(root == null){
			throw new Exception(Messages.IODataManager_missingKey+t);
		}
		
		boolean isAnyType = TypeManager.isAnyType(t);
			
		if((isAnyType || TypeManager.isStringType(t)) && root.getName().equals("string")){ //$NON-NLS-1$
			return true;
		}else if((isAnyType || TypeManager.isBooleanType(t)) && 
				(root.getName().equals("true") || root.getName().equals("false"))){  //$NON-NLS-1$ //$NON-NLS-2$
			return true;
		}else if((isAnyType || TypeManager.isBinaryType(t)) && root.getName().equals("file")){ //$NON-NLS-1$
			return true;
		}else if((isAnyType || TypeManager.isCollectionType(t)) && root.getName().equals("collection")){ //$NON-NLS-1$
			Collection<Element> children = root.getChildren();
			Element c1 = null;
			Element c2 = null;
			for(Element child : children){
				if(!isValidDataElem(child,isAnyType?TypeManager.getAnyType():((CollectionType)t).getSubtypeContainer().getType())){
					return false;
				}
				c1 = c2;
				c2 = child;
				if(c1!=null && c2!=null &&
						((("true".equals(c1.getName()) || "false".equals(c1.getName())) && !"true".equals(c2.getName()) && !"false".equals(c2.getName())) //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
						|| ("string".equals(c1.getName()) && !"string".equals(c2.getName())) //$NON-NLS-1$ //$NON-NLS-2$
						|| ("collection".equals(c1.getName()) && !"collection".equals(c2.getName())) //$NON-NLS-1$ //$NON-NLS-2$
						|| ("map".equals(c1.getName()) && !"map".equals(c2.getName())) //$NON-NLS-1$ //$NON-NLS-2$
					)){
					throw new Exception(Messages.IODataManager_collectionWrongType);
				}
			}
			return true;
		}else if((isAnyType || TypeManager.isMapType(t)) && root.getName().equals("map")){ //$NON-NLS-1$
			Collection<Element> children = root.getChildren();
			Set<String> checkedKeys = new HashSet<String>();
			for(Element entry : children){
				if(!entry.getName().equals("entry")){ //$NON-NLS-1$
					throw new Exception(Messages.IODataManager_mapWrongSubkey1+entry.getName()+Messages.IODataManager_mapWrongSubkey2);
				}
				String key = entry.getChildText("key"); //$NON-NLS-1$
				Element value = entry.getChild("value"); //$NON-NLS-1$
				if(checkedKeys.contains(key)){
					throw new Exception(Messages.IODataManager_mapKeys1+key+Messages.IODataManager_mapKeys2);
				}
				if(value == null || value.getChildren().size()==0){
					throw new Exception(Messages.IODataManager_mapKeys3+key+Messages.IODataManager_mapKeys4);
				}
				if(!isValidDataElem((Element) value.getChildren().get(0), isAnyType?TypeManager.getAnyType():(((MapType)t).getSubtype(key)!=null?((MapType)t).getSubtype(key).getType():TypeManager.getAnyType()))){
					return false;
				}
			}
			return true;
		}else if(root.getName().equals("any")){ //$NON-NLS-1$
			throw new Exception(Messages.IODataManager_any);
		}else if(root.getName().equals("truefalse")){ //$NON-NLS-1$
			throw new Exception(Messages.IODataManager_trueFalse);
		}else{
			return false;
		}
	}
	
	
	/**
	 * pobiera z wejcia dane o zdefiniowanym typie t
	 * @param inputDir	- katalog z plikami wejsciowymi
	 * @param root		- element glowny wejscia
	 * @param t			- typ danych wymagany na wejsciu
	 * @return
	 * @throws Exception
	 */
	@SuppressWarnings("unchecked") //$NON-NLS-1$
	public static Object loadDataObject(String fileName, Type t) throws Exception{
		SAXBuilder builder = new SAXBuilder(false);
		Document document;
		try {
			document = builder.build(fileName);
		} catch (JDOMException e) {
			throw new Exception(Messages.IODataManager_inputNotXml, e);
		} catch (Exception e) {
			throw e;
		}

		// load data
		Element dataElem = getObjectElem(document.getRootElement());
		if (dataElem == null) {
			throw new Exception(Messages.IODataManager_0);
		}
		if (!isValidDataElem(dataElem, t)) {
			throw new Exception(Messages.IODataManager_inputFormat);
		}
		return getDataObject(FilenameUtils.getFullPath(fileName), dataElem);
	}
	
	@SuppressWarnings("unchecked") //$NON-NLS-1$
	public static Map<Place,Collection<Token>> loadDiagramTokens(String fileName, DflDiagram diagram) throws Exception{
		SAXBuilder builder = new SAXBuilder(false);
		Document document;
		try {
			document = builder.build(fileName);
		} catch (JDOMException e) {
			throw new Exception(Messages.IODataManager_inputNotXml, e);
		} catch (Exception e) {
			throw e;
		}

		 Set<PlaceModelElement> places = diagram.getPlaces();
		 Map<String,Place> placesMap = new HashMap<String, Place>();
		 for(PlaceModelElement p : places){
			 placesMap.put(p.getPlace().getId(), p.getPlace());
		 }
		
		// load data
		Map<Place,Collection<Token>> tokens = new HashMap<Place,Collection<Token>>();
		List<Element> tokenElems = document.getRootElement().getChildren("token"); //$NON-NLS-1$
		for(Element tokenElem : tokenElems){
			String placeId = tokenElem.getAttributeValue("placeId"); //$NON-NLS-1$
			if(placesMap.containsKey(placeId)){
				Place place = placesMap.get(placeId);
				if(!tokens.containsKey(place)){
					tokens.put(place, new ArrayList<Token>());
				}
				tokens.get(place).add(loadToken(tokenElem,fileName,place.getTypeContainer().getType()));
			}
		}
		return tokens;
	}
	
	@SuppressWarnings("unchecked") //$NON-NLS-1$
	public static Collection<Token> loadFlatTokens(String fileName, Type t) throws Exception{
		SAXBuilder builder = new SAXBuilder(false);
		Document document;
		try {
			document = builder.build(fileName);
		} catch (JDOMException e) {
			throw new Exception(Messages.IODataManager_inputNotXml, e);
		} catch(Exception e){
			throw e;
		} 

		// load data
		Collection<Token> tokens = new ArrayList<Token>();
		List<Element> tokenElems = document.getRootElement().getChildren("token"); //$NON-NLS-1$
		for(Element tokenElem : tokenElems){
			tokens.add(loadToken(tokenElem,fileName,t));
		}
		return tokens;
	}
	
	@SuppressWarnings("unchecked") //$NON-NLS-1$
	private static Token loadToken(Element tokenElem, String fileName, Type t) throws Exception {
		// load data
		Element dataElem = null;
		List<Element> children = tokenElem.getChildren();
		for(Element child : children){
			dataElem = getObjectElem(child);
			if(dataElem!=null){
				break;
			}
		}
		if (dataElem == null) {
			throw new Exception(Messages.IODataManager_7);
		}
		if (!isValidDataElem(dataElem, t)) {
			throw new Exception(Messages.IODataManager_inputFormat);
		}
		
		Token token = new Token(getDataObject(FilenameUtils.getFullPath(fileName), dataElem));
		
		String activeAtt = tokenElem.getAttributeValue("active"); //$NON-NLS-1$
		token.setActive(activeAtt==null || activeAtt.equals("yes"));
		
		token.setHistory(loadHistory(FilenameUtils.getFullPath(fileName), tokenElem.getChild("history"))); //$NON-NLS-1$
		
		return token;
	}

	@SuppressWarnings("unchecked") //$NON-NLS-1$
	private static History loadHistory(String inputDir, Element root) throws Exception{
		History history = new History();
		if(root!=null){
			Element col = root.getChild("collection"); //$NON-NLS-1$
			Collection<Element> children = col.getChildren();
			Collection c = new ArrayList();
			Object selectedColElem = null;
			for(Element child : children){
				Object colElem = getDataObject(inputDir,child);
				c.add(colElem);
				if("true".equals(child.getAttributeValue("selected"))){ //$NON-NLS-1$ //$NON-NLS-2$
					selectedColElem = colElem;
				}
			}
			history.setCol(c);
			
			if(selectedColElem==null && "true".equals(col.getAttributeValue("selected"))){
				history.setColElem(history.getCol());
			}else{
				history.setColElem(selectedColElem);
			}
			
			if(root.getChild("history")!=null){ //$NON-NLS-1$
				history.setHistory(loadHistory(inputDir, root.getChild("history"))); //$NON-NLS-1$
			}
		}
		return history;
	}

	private static Element getObjectElem(Element root){
		if(root == null) return null;
		String n = root.getName();
		if("token".equals(n)){ //$NON-NLS-1$
			return root.getChild("data"); //$NON-NLS-1$
		}else if("string".equals(n) || "true".equals(n) || "false".equals(n) || "file".equals(n) ||  "collection".equals(n) || "map".equals(n)){ //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$
			return root;
		}else {
			return null;
		}
	}
	
	/**
	 * pobiera dane o typie t z drzewa elementow wejscia i zwraca gotowy obiekt przekazywany do przeplywu
	 * @param inputDir	- katalog z plikami wejsciowymi
	 * @param root		- element glowny wejscia
	 * @param t			- typ danych wymagany na wejsciu
	 * @return
	 * @throws Exception
	 */
	@SuppressWarnings("unchecked") //$NON-NLS-1$
	public static Object getDataObject(String inputDir, Element root) throws Exception{
		if(root.getName().equals("string")){ //$NON-NLS-1$
			return root.getText();
		}else if(root.getName().equals("true")){ //$NON-NLS-1$
			return Boolean.TRUE;
		}else if(root.getName().equals("false")){ //$NON-NLS-1$
			return Boolean.FALSE;
		}else if(root.getName().equals("file")){ //$NON-NLS-1$
			try {
				String fileName = FilenameUtils.normalize(inputDir+"/"+root.getTextTrim()); //$NON-NLS-1$
				FileInputStream fileinputstream = new FileInputStream(new File(fileName));

	            int numberBytes = fileinputstream.available();
	            byte bytearray[] = new byte[numberBytes];
	            fileinputstream.read(bytearray);
	            return bytearray;
			} catch (FileNotFoundException e) {
				throw new Exception(Messages.IODataManager_openFile1+root.getTextTrim()+Messages.IODataManager_openFile2);
			}
		}else if(root.getName().equals("collection")){ //$NON-NLS-1$
			Collection<Element> children = root.getChildren();
			Collection c = new ArrayList();
			for(Element child : children){
				c.add(getDataObject(inputDir,child));
			}
			return c;
		}else if(root.getName().equals("map")){ //$NON-NLS-1$
			Collection<Element> children = root.getChildren();
			Map<String,Object> m = new HashMap<String,Object>();
			for(Element entry : children){
				String key = entry.getChildText("key"); //$NON-NLS-1$
				List values = entry.getChild("value").getChildren(); //$NON-NLS-1$
				m.put(key,getDataObject(inputDir,(Element) values.get(0)));
			}
			return m;
		}else {
			// any type :)
			return root.getText();
		}
	}
	
	
	/**
	 * pobiera domyslny schemat danych wejsciowych dla danego typu
	 * @param t	- oczekiwany typ wejscia
	 * @return
	 * @throws Exception
	 */
	public static Element getDataSchemaElement(Type t) throws Exception{
		if(TypeManager.isAnyType(t)){
			return new Element("any"); //$NON-NLS-1$
		}else if(TypeManager.isStringType(t)){
			return new Element("string"); //$NON-NLS-1$
		}else if(TypeManager.isBooleanType(t)){
			return new Element("truefalse"); //$NON-NLS-1$
		}else if(TypeManager.isBinaryType(t)){
			return new Element("file"); //$NON-NLS-1$
		}else if(TypeManager.isCollectionType(t)){
			Element c = new Element("collection"); //$NON-NLS-1$
			c.addContent(getDataSchemaElement(((CollectionType)t).getSubtypeContainer().getType()));
			return c;
		}else if(TypeManager.isMapType(t)){
			Element m = new Element("map"); //$NON-NLS-1$
			
			Set<String> keys = ((MapType)t).getSubkeysNames();
			for(String key : keys){
				Element e = new Element("entry"); //$NON-NLS-1$
				m.addContent(e);
				
				Element k = new Element("key"); //$NON-NLS-1$
				k.setText(key);
				e.addContent(k);
				
				Element v = new Element("value"); //$NON-NLS-1$
				v.addContent(getDataSchemaElement(((MapType)t).getSubtype(key).getType()));
				e.addContent(v);
			}
			return m;
		}else{
			throw new Exception(Messages.IODataManager_unknownType+t);
		}
	}
	
	
	
	/**
	 * zapisywanie danych wyjsciowych w danym katalogu. Dane zostaja zapisane w pliku o nazwie w formacie DDMMYYY.xml
	 * w ktorym zapisane jest takze wejscie. Dodatkowo w danym katalogu zapisywane sa dodatkowe pliki binarne 
	 * wygenerowane przez przeplyw i zawarte w danych wyjsciowych
	 * 
	 * @param outputDirName	- katalog, gdzie zapisac dane wyjsciowe
	 * @param outputData	- dane do zapisania
	 * @param outputType	- format zapisywanych danych
	 * @throws Exception
	 */
	@SuppressWarnings("unchecked") //$NON-NLS-1$
	public static void saveTokens(String filePath, Map<String,Collection<Token>> tokens) throws Exception{
		String outputDirName = FilenameUtils.getFullPath(filePath);
		String extention = FilenameUtils.getExtension(filePath);
		String extraFilesDir = (extention==null||extention.equals(""))?filePath:filePath.substring(0,filePath.length()-extention.length()-1); //$NON-NLS-1$
		if(extraFilesDir.endsWith("\\") || extraFilesDir.endsWith("/")){
			extraFilesDir=extraFilesDir.substring(0,extraFilesDir.length()-1);
		}
		extraFilesDir+="_files/";
		filePath = (extention==null||extention.equals(""))?filePath+".xml":filePath; //$NON-NLS-1$ //$NON-NLS-2$
		
		// create the output dir
		try{
			(new File(outputDirName)).mkdirs();
		} catch (Exception e) {
		}

		Set<String> placesIds = tokens.keySet();
		for (String placeId : placesIds) {
			Collection<Token> ttokens = tokens.get(placeId);
			boolean temp = false;
			for (Token token : ttokens) {
				if (hasBinaryData(token.getData())) {
					if (new File(extraFilesDir).exists()
							&& !DialogsManager.openOkCancelDialog(
									"Directory exist",
									"Directory for binary files "
											+ extraFilesDir
											+ " exist. Override?")) {
						return;
					} else {
						try {
							(new File(extraFilesDir)).mkdirs();
						} catch (Exception e) {
						}

						temp = true;
						break;
					}
				}
			}
			if (temp)
				break;
		}
		
		Map<Object,String> storedFiles = new HashMap<Object, String>();
		
		Element rootElem = new Element("tokens"); //$NON-NLS-1$
		for(String placeId : placesIds){
			Collection<Token> ttokens = tokens.get(placeId);
			for(Token token : ttokens){
				Element tokenElem = new Element("token"); //$NON-NLS-1$
				tokenElem.setAttribute("placeId",placeId); //$NON-NLS-1$
				tokenElem.setAttribute("active",token.isActive()?"yes":"no"); //$NON-NLS-1$
				tokenElem.addContent(getSavedHistory(extraFilesDir,token.getHistory(),storedFiles));
				tokenElem.addContent(getSavedData(extraFilesDir, token.getData(), storedFiles));
				rootElem.addContent(tokenElem);
			}
		}
		Document document = new Document();
		document.addContent(rootElem);

		// save
		FileOutputStream fos = new FileOutputStream(filePath); //$NON-NLS-1$ //$NON-NLS-2$
		new XMLOutputter(Format.getPrettyFormat()).output(document, fos);
		fos.close();
	}
	
	@SuppressWarnings("unchecked")
	private static boolean hasBinaryData(Object o){
		if(o instanceof byte[]){
			return true;
		}else if(o instanceof Collection){
			Collection oo = (Collection) o;
			for(Object x : oo){
				if(hasBinaryData(x)){
					return true;
				}
			}
		}else if(o instanceof Map){
			Map m = (Map) o;
			for(Object key : m.keySet()){
				if(hasBinaryData(m.get(key))){
					return true;
				}
			}
		}
		return false;
	}
	
	/**
	 * zapisywanie danych wyjsciowych w danym katalogu. Dane zostaja zapisane w pliku o nazwie w formacie DDMMYYY.xml
	 * w ktorym zapisane jest takze wejscie. Dodatkowo w danym katalogu zapisywane sa dodatkowe pliki binarne 
	 * wygenerowane przez przeplyw i zawarte w danych wyjsciowych
	 * 
	 * @param outputDirName	- katalog, gdzie zapisac dane wyjsciowe
	 * @param outputData	- dane do zapisania
	 * @param outputType	- format zapisywanych danych
	 * @throws Exception
	 */
	@SuppressWarnings("unchecked")//$NON-NLS-1$
	public static void saveDataXmlFile(String filePath, final Object data)
			throws Exception {
		String outputDirName = FilenameUtils.getFullPath(filePath);
		String extention = FilenameUtils.getExtension(filePath);
		String extraFilesDir = (extention == null || extention.equals("")) ? filePath : filePath.substring(0, filePath.length() - extention.length() - 1); //$NON-NLS-1$
		if (extraFilesDir.endsWith("\\") || extraFilesDir.endsWith("/")) {
			extraFilesDir = extraFilesDir.substring(0,
					extraFilesDir.length() - 1);
		}
		extraFilesDir += "_files/";
		filePath = (extention == null || extention.equals("")) ? filePath + ".xml" : filePath; //$NON-NLS-1$ //$NON-NLS-2$

		// create the output dir
		try {
			(new File(outputDirName)).mkdirs();
		} catch (Exception e) {
		}

		if (hasBinaryData(data)) {
			if (new File(extraFilesDir).exists()
					&& !DialogsManager.openOkCancelDialog(
							"Directory exist",
							"Directory for binary files " + extraFilesDir
									+ " exist. Override?")) {
				return;
			} else {
				try {
					(new File(extraFilesDir)).mkdirs();
				} catch (Exception e) {
				}
			}
		}

		Element dataElem = getSavedData(extraFilesDir, data, new HashMap()); //$NON-NLS-1$
		Document document = new Document();
		document.addContent(dataElem);

		// save
		FileOutputStream fos = new FileOutputStream(filePath); //$NON-NLS-1$ //$NON-NLS-2$
		new XMLOutputter(Format.getPrettyFormat()).output(document, fos);
		fos.close();
	}

	public static void saveBinaryFile(String fileLocation, byte[] data)
			throws Exception {
		FileOutputStream fileoutputstream = new FileOutputStream(fileLocation);
		for (int i = 0; i < data.length; i++) {
			fileoutputstream.write(data[i]);
		}
		fileoutputstream.close();
	}
	
	public static void saveTextFile(String fileLocation, String str) throws Exception{
		FileOutputStream fileoutputstream = new FileOutputStream(fileLocation);
		fileoutputstream.write(str.getBytes());
        fileoutputstream.close();
	}

	private static Element getSavedHistory(String extraFilesDir, History history, Map<Object, String> storedFiles) throws Exception {
		Element root = new Element("history"); //$NON-NLS-1$
		root.addContent(getSavedHistoryCol(extraFilesDir,history.getColElem(),history.getCol(),storedFiles));
		if(history.getHistory()!=null){
			root.addContent(getSavedHistory(extraFilesDir,history.getHistory(),storedFiles));
		}
		return root;
	}
	
	@SuppressWarnings("unchecked") //$NON-NLS-1$
	private static Element getSavedHistoryCol(String extraFilesDir, Object colElem,
			Collection col, Map<Object, String> storedFiles) throws Exception {
		Element root = new Element("collection"); //$NON-NLS-1$
		if(colElem == col){
			root.setAttribute("selected","true");
		}
		for(Object e : col){
			Element elem = getSavedData(extraFilesDir, e, storedFiles);
			if(colElem != col && Utils.equals(e, colElem)) elem.setAttribute("selected","true"); //$NON-NLS-1$ //$NON-NLS-2$
			root.addContent(elem);
		}
		return root;
	}

	public static String getXMLRepresentaion(Object dataToStore) throws Exception{
		Element elem = getSavedData("/tmp/", dataToStore, new HashMap<Object, String>(), false);
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		new XMLOutputter(Format.getPrettyFormat()).output(elem, baos);
		return new String(baos.toByteArray());
	}

	private static Element getSavedData(String extraFilesDir, Object dataToStore, Map<Object,String> storedFiles) throws Exception{
		return getSavedData(extraFilesDir, dataToStore, storedFiles, true);
	}
	@SuppressWarnings("unchecked") //$NON-NLS-1$
	private static Element getSavedData(String extraFilesDir, Object dataToStore, Map<Object,String> storedFiles, boolean saveFiles) throws Exception{
		Element elem = null;
		if(dataToStore instanceof String){
			elem = new Element("string"); //$NON-NLS-1$
			elem.addContent(dataToStore.toString());
		}else if(dataToStore instanceof Boolean){
			if(((Boolean)dataToStore).booleanValue()){
				elem = new Element("true"); //$NON-NLS-1$
			}else{
				elem = new Element("false"); //$NON-NLS-1$
			}
		}else if(dataToStore instanceof Collection){
			elem = new Element("collection"); //$NON-NLS-1$
			Collection col = (Collection) dataToStore;
			for(Object e : col){
				elem.addContent(getSavedData(extraFilesDir, e, storedFiles, saveFiles));
			}
		}else if(dataToStore instanceof Map){
			elem = new Element("map"); //$NON-NLS-1$
			Map m = (Map) dataToStore;
			Set<String> keys = m.keySet();
			for(String key : keys){
				Element e = new Element("entry"); //$NON-NLS-1$
				elem.addContent(e);
				
				Element k = new Element("key"); //$NON-NLS-1$
				k.setText(key);
				e.addContent(k);
				
				Element v = new Element("value"); //$NON-NLS-1$
				v.addContent(getSavedData(extraFilesDir,m.get(key),storedFiles, saveFiles));
				e.addContent(v);
			}
		}else if(dataToStore instanceof byte[] && storedFiles.containsKey(dataToStore)){
			elem = new Element("file"); //$NON-NLS-1$
			elem.setText(FilenameUtils.getName(storedFiles.get(dataToStore)));
		}else if(dataToStore instanceof byte[]){
			String ext = ImageUtils.getFormatFromStream((byte[]) dataToStore);
			if(ext==null || ext.equals("")){ //$NON-NLS-1$
				ext = "dat"; //$NON-NLS-1$
			}
			int fileCounter = 1;
			String fileName = "";  //$NON-NLS-1$
			boolean fileExist = false;
			do{
				fileName = fileCounter+"."+ext.toLowerCase(); //$NON-NLS-1$ //$NON-NLS-2$
				fileCounter++;
				fileExist = false;
				for(Object key : storedFiles.keySet()){
					if(storedFiles.get(key).equals(fileName)){
						fileExist = true;
						break;
					}
				}
				if(!fileExist && new File(extraFilesDir+"/"+fileName).exists()){
		        	fileExist = true;
				}
			}while(fileExist);

			if(saveFiles){
				saveBinaryFile(extraFilesDir+"/"+fileName, (byte[]) dataToStore); //$NON-NLS-1$
			}
			storedFiles.put(dataToStore,fileName);
			
			String prefix = new File(extraFilesDir).getParent();
			String localDir = extraFilesDir.substring(prefix.length());
			elem = new Element("file"); //$NON-NLS-1$
			elem.setText(localDir+"/"+fileName);
		}else{
			elem = new Element("null"); //$NON-NLS-1$
//			throw new Exception(Messages.IODataManager_unknownResultDataType+(dataToStore==null?"NULL":dataToStore.getClass())); //$NON-NLS-2$ //$NON-NLS-1$
		}
		return elem;
	}

}
